﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.DataGrid
{
  internal abstract class VirtualContainerBase : DependencyObject, IDataGridElement, ILayoutContainer, ILayoutElement
  {
    internal static readonly XceedRoutedEvent ReleaseElementEvent = new XceedRoutedEvent();


    public abstract UIElement Visual
    {
      get;
    }

    public abstract DataPath Path
    {
      get;
    }

    public object ReferenceItem
    {
      get
      {
        return this.Path;
      }
    }

    public Size DesiredSize
    {
      get
      {
        Size headers = this.HeadersDesiredSize;
        Size footers = this.FootersDesiredSize;
        Size elements = this.PanelDesiredSize;
        elements.Width = Math.Max( headers.Width, elements.Width );
        elements.Width = Math.Max( footers.Width, elements.Width );
        elements.Height += ( headers.Height + footers.Height );

        return elements;
      }
    }

    internal IEnumerable<ILayoutElement> RenderedElements
    {
      get
      {
        return this.ScrollableElements
          .Union( this.HeadersFooters.Cast<ILayoutElement>() );
      }
    }

    internal IEnumerable<HeaderFooterWrapper> HeadersFooters
    {
      get
      {
        return this.Headers.Union( this.Footers );
      }
    }

    internal IEnumerable<ILayoutElement> ScrollableElements
    {
      get
      {
        return this.Layout.Elements;
      }
    }

    internal IEnumerable<HeaderFooterWrapper> Headers
    {
      get
      {
        return m_headers;
      }
    }

    internal IEnumerable<HeaderFooterWrapper> Footers
    {
      get
      {
        return m_footers;
      }
    }

    internal Size HeadersDesiredSize
    {
      get
      {
        return this.GetElementsDesiredSize( this.Headers.Cast<ILayoutElement>() );
      }
    }

    internal Size FootersDesiredSize
    {
      get
      {
        return this.GetElementsDesiredSize( this.Footers.Cast<ILayoutElement>() );
      }
    }

    private Size PanelDesiredSize
    {
      get
      {
        return this.Layout.DesiredSize;
      }
    }

    public ILayout Layout
    {
      get
      {
        return m_layout;
      }
    }

    internal IEnumerable<ILayoutElement> Elements
    {
      get
      {
        return this.Layout.Elements;
      }
    }

    private Rect? LastArrangeRect
    {
      get;
      set;
    }

    private Rect PanelArrangeRect
    {
      get
      {
        Rect panelRect = m_arrangeParameters.ArrangePosition;
        Size headerSize = this.HeadersDesiredSize;
        Size footerSize = this.HeadersDesiredSize;
        panelRect.Y += headerSize.Height;
        panelRect.Height = Math.Max( 0d, panelRect.Height - ( headerSize.Height + footerSize.Height ) );
        return panelRect;
      }
    }

    private Size GetElementsDesiredSize( IEnumerable<ILayoutElement> elements )
    {
      Size size = new Size( 0, 0 );
      foreach( ILayoutElement element in elements )
      {
        Size desiredSize = element.DesiredSize;
        size.Width = Math.Max( size.Width, desiredSize.Width );
        size.Height += desiredSize.Height;
      }

      return size;
    }

    public abstract void SetDataPath( Data.DataPath path );

    internal virtual void SetHeadersFooters( IEnumerable<HeaderFooterWrapper> headers, IEnumerable<HeaderFooterWrapper> footers )
    {
      m_headers = headers;
      m_footers = footers;
    }

    private LayoutUpdateResult SetLayoutCore( ILayout oldLayout, ILayout newLayout )
    {
      if( oldLayout == null )
        throw new ArgumentNullException( "oldLayout" );

      if( newLayout == null )
        throw new ArgumentNullException( "newLayout" );

      IEnumerable<ILayoutElement> addedElements = newLayout.Elements.Except( oldLayout.Elements ).ToList();
      IEnumerable<ILayoutElement> removedElements = oldLayout.Elements.Except( newLayout.Elements ).ToList();

      return new LayoutUpdateResult( addedElements, removedElements );
    }

    internal LayoutUpdateResult SetLayout( ILayout layout )
    {
      if( m_previousLayout != null )
        throw new DataGridInternalException();

      m_previousLayout = m_layout;
      m_layout = layout;

      return this.SetLayoutCore( m_previousLayout, m_layout );
    }

    internal void CommitLayout()
    {
      m_previousLayout = null;
    }

    internal LayoutUpdateResult RollbackLayout()
    {
      if( m_previousLayout == null )
        return new LayoutUpdateResult( new ILayoutElement[ 0 ], new ILayoutElement[ 0 ] );

      LayoutUpdateResult result = this.SetLayoutCore( m_layout, m_previousLayout );

      m_layout = m_previousLayout;
      m_previousLayout = null;

      return result;
    }

    internal void ClearLayout()
    {
      Debug.Assert( m_previousLayout == null );
      m_layout = EmptyLayout.Instance;
    }

    protected virtual void MeasureElement( ILayoutElement element, Size availableSize )
    {
      element.Measure( availableSize );
    }

    protected virtual void ArrangeElement( ILayoutElement element, Rect finalRect, LayoutParameters parameters )
    {
      if (element is UIElement)
      {
        Canvas.SetZIndex((UIElement)element, DataGridControl.DefaultTopLevelGroupZIndex - (this.Path.Depth + 1));
      }

      Rect arrangeRect = finalRect.AddPosition( this.PanelArrangeRect.GetPosition() );

      Rect visibleBounds = m_arrangeParameters.VisibleBounds;
      Size headerSize = this.HeadersDesiredSize;
      visibleBounds.Y += headerSize.Height;
      visibleBounds.Height -= headerSize.Height;
      visibleBounds.Height -= this.FootersDesiredSize.Height;
      visibleBounds.Height = Math.Max(0d, visibleBounds.Height);
      element.Arrange( new ArrangeParameters( arrangeRect, visibleBounds, parameters ) );
    }

    internal void Measure( Size availableSize )
    {
      Size measuredSize = new Size( 0, 0 );
      Size desiredSize;

      Size headerFooterMeasureSize = new Size( availableSize.Width, double.PositiveInfinity );
      Action<ILayoutElement> measureHeaderFooter = ( element ) =>
      {
        element.Measure( headerFooterMeasureSize );
        desiredSize = element.DesiredSize;
        measuredSize.Height += desiredSize.Height;
        measuredSize.Width = Math.Max( desiredSize.Width, measuredSize.Width );
        availableSize.Height = Math.Max( 0d, availableSize.Height - desiredSize.Height );
      };

      foreach( ILayoutElement element in m_headers )
      {
        measureHeaderFooter(element);
      }

      foreach( ILayoutElement element in m_footers )
      {
        measureHeaderFooter( element );
      }

      desiredSize = this.MeasurePanelOverride(availableSize);
      measuredSize.Height += desiredSize.Height;
      measuredSize.Width = Math.Max( desiredSize.Width, measuredSize.Width );
    }

    internal Size Arrange( ArrangeParameters parameters )
    {
      this.LastArrangeRect = parameters.ArrangePosition;
      m_arrangeParameters = parameters;
      Rect finalRect = parameters.ArrangePosition;

      double headersHeight = this.HeadersDesiredSize.Height;
      double footersHeight = this.FootersDesiredSize.Height;
      double offsetLimit = finalRect.Height - (footersHeight + headersHeight);

      Rect arrangeRect = new Rect(0,0,finalRect.Width,0);
      double availableLength = finalRect.Height;
      double headerArrangeLimit = parameters.VisibleBounds.Y;
      foreach( ILayoutElement element in m_headers )
      {
        Size desiredSize = element.DesiredSize;
        arrangeRect.Height = desiredSize.Height;
        availableLength -= desiredSize.Height;
        this.ArrangeHeader(element, arrangeRect, headerArrangeLimit, offsetLimit);
        headerArrangeLimit += desiredSize.Height;
        arrangeRect.Y += desiredSize.Height;
      }

      double headerLength = arrangeRect.Y;


      arrangeRect.Y = finalRect.Height;
      double footerArrangeLimit = parameters.VisibleBounds.Y + parameters.VisibleBounds.Height;
      foreach( ILayoutElement element in m_footers )
      {
        Size desiredSize = element.DesiredSize;
        arrangeRect.Height = desiredSize.Height;
        availableLength -= arrangeRect.Height;
        arrangeRect.Y -= desiredSize.Height;
        this.ArrangeFooter(element, arrangeRect, footerArrangeLimit, offsetLimit);
        footerArrangeLimit -= desiredSize.Height;
      }

      arrangeRect.Y = headerLength;
      arrangeRect.Height = Math.Max( 0d, availableLength );

      this.ArrangePanelOverride( arrangeRect.GetSize(), m_arrangeParameters.LayoutParameters );

      return finalRect.GetSize();
    }

    private void ArrangeHeader(ILayoutElement element, Rect finalRect, double arrangeLimit, double offsetLimit )
    {
      this.ArrangeHeaderFooter(element, 
        finalRect,
        new ArrangeLimit(arrangeLimit, true),
        new OffsetLimits(double.NegativeInfinity, offsetLimit));
    }

    private void ArrangeFooter(ILayoutElement element, Rect finalRect, double arrangeLimit, double offsetLimit)
    {
      this.ArrangeHeaderFooter(element,
        finalRect,
        new ArrangeLimit(arrangeLimit, false),
        new OffsetLimits(-offsetLimit, double.PositiveInfinity));
    }

    private void ArrangeHeaderFooter( ILayoutElement element, Rect finalRect, ArrangeLimit arrangeLimit, OffsetLimits offsetLimits )
    {
      Canvas.SetZIndex((UIElement)element, DataGridControl.DefaultTopLevelGroupZIndex - this.Path.Depth);
      finalRect = finalRect.AddPosition(m_arrangeParameters.ArrangePosition.GetPosition());

      ArrangeParameters parameters = new ArrangeParameters(
        finalRect,
        arrangeLimit,
        offsetLimits,
        m_arrangeParameters.LayoutParameters );

      element.Arrange( parameters );
    }

    internal virtual Size MeasurePanelOverride( Size availableSize )
    {
      return this.Layout.MeasurePanel( availableSize );
    }

    internal virtual Size ArrangePanelOverride( Size finalPosition, LayoutParameters parameters )
    {
      return this.Layout.ArrangePanel( finalPosition, parameters );
    }

    internal ViewportInfo GetViewportInfo( Rect viewportRect )
    {
      Size panelDesiredSize = this.PanelDesiredSize;
      Rect panelRect = viewportRect;
      double headersFootersHeight = this.HeadersDesiredSize.Height + this.FootersDesiredSize.Height;
      panelRect.Height = Math.Max( 0d, viewportRect.Height - headersFootersHeight );

      return this.Layout.GetViewportInfo( panelRect );
    }

    internal virtual void Accept( IVisitor visitor )
    {
      visitor.Visit( this );
    }

    internal void ApplyToAllChildren( Action<ILayoutElement> a )
    {
      this.RenderedElements.ToList().ForEach( a );
    }

    internal void ApplyToHeadersFooters( Action<HeaderFooterWrapper> a )
    {
      foreach( HeaderFooterWrapper element in this.Headers.Union( this.Footers ) )
      {
        a(element);
      }
    }

#if SILVERLIGHT
    public override int GetHashCode()
    {
      return this.Path.GetHashCode();
    }

    public override bool Equals( object obj )
    {
      VirtualContainerBase container = obj as VirtualContainerBase;
      if(container == null)
        return false;

      return object.Equals( this.Path, container.Path );
    }
#endif


    private IEnumerable<HeaderFooterWrapper> m_headers = new HeaderFooterWrapper[ 0 ];
    private IEnumerable<HeaderFooterWrapper> m_footers = new HeaderFooterWrapper[ 0 ];
    private ArrangeParameters m_arrangeParameters;
    private ILayout m_layout = EmptyLayout.Instance;
    private ILayout m_previousLayout = null;

    #region IDataGridElement Members

    Data.DataPath IDataGridElement.Path
    {
      get { return this.Path; }
    }

    void IDataGridElement.SetDataPath( Data.DataPath path )
    {
      this.SetDataPath( path );
    }

    #endregion

    #region IVisitorElement Members

    void IVisitorElement.Accept( IVisitor visitor )
    {
      this.Accept( visitor );
    }

    #endregion

    #region ILayoutContainer Members

    void ILayoutContainer.MeasureElement( ILayoutElement element, Size availableSize )
    {
      this.MeasureElement( element, availableSize );
    }

    void ILayoutContainer.ArrangeElement( ILayoutElement element, Rect finalPosition, LayoutParameters parameters )
    {
      this.ArrangeElement( element, finalPosition, parameters );
    }

    #endregion

    #region ILayoutElement Members

    object ILayoutElement.ReferenceItem
    {
      get
      {
        return this.ReferenceItem;
      }
    }

    Size ILayoutElement.DesiredSize
    {
      get { return this.DesiredSize; }
    }

    Rect? ILayoutElement.LastArrangeRect
    {
      get
      {
        return this.LastArrangeRect;
      }
      set
      {
        this.LastArrangeRect = value;
      }
    }

    void ILayoutElement.Measure( Size availableSize )
    {
      this.Measure( availableSize );
    }

    void ILayoutElement.Arrange( ArrangeParameters parameters )
    {
      this.Arrange(parameters);
    }

    #endregion
  }
}
